package com.bizihang.common.security.server;

import com.bizihang.common.security.core.properties.SecurityProperties;
import com.bizihang.common.security.core.properties.server.OAuth2ClientProperties;
import org.apache.commons.lang3.ArrayUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.oauth2.config.annotation.builders.InMemoryClientDetailsServiceBuilder;
import org.springframework.security.oauth2.config.annotation.configuration.ClientDetailsServiceConfiguration;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.*;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.endpoint.*;
import org.springframework.security.oauth2.provider.token.*;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;

import java.util.ArrayList;
import java.util.List;

/**
 * 认证服务器配置
 * {@link EnableAuthorizationServer}默认注册了一个{@link AuthorizationServerEndpointsConfiguration}(认证服务器的认证端配置)
 * 和{@link AuthorizationServerSecurityConfiguration}(认证端的安全配置)。
 * <p>
 * {@link AuthorizationServerEndpointsConfiguration}，如果ApplicationContext有多个{@link AuthorizationServerConfigurer}会自动合并，
 * 配置包括：
 * <ul>
 * <li>{@link AuthorizationEndpoint}:获取token或者code。自动从{@link org.springframework.security.oauth2.provider.endpoint.TokenEndpoint}获取token,默认的
 * 授权模式是：response_type=token。！！！/oauth/authorize的最低安全要求：经过认证的用户才可以访问</li>
 * <li>{@link TokenEndpoint}:/oauth/token获取token,注意看他的api介绍——BASIC认证</>
 * <li>{@link CheckTokenEndpoint},/oauth/check_token验证Token</li>
 * <li>{@link WhitelabelApprovalEndpoint},/oauth/confirm_access用户授权范围页面</li>
 * <li>{@link WhitelabelErrorEndpoint},/oauth/error错误页面</li>
 * <li>{@link FrameworkEndpointHandlerMapping},一般人写不出这个，很强！</li>
 * <li>{@link ConsumerTokenServices}</li>
 * <li>{@link AuthorizationServerTokenServices}</li>
 * </ul>
 * <p></p>
 * {@link AuthorizationServerSecurityConfiguration}认证服务器的安全配置，包括：
 * <ul>
 * <li>认证服务器的安全配置其实是一个{@link WebSecurityConfigurerAdapter},order=0，有没有不明觉厉的感觉？推测资源服务器的安全配置应该一样。
 * 认证服务器默认配置的安全策略是: /oauth/token，请求需完全认证。</li>
 * <li>{@link ClientDetailsServiceConfiguration}</li>
 * <li>{@link AuthorizationServerEndpointsConfiguration}</li>
 * </ul>
 * <p>
 * 以后配置器的名字应该叫做XxxConfigurer{@link AuthorizationServerSecurityConfigurer},我需要把我定义的配置器修改一下名字。
 * {@link AuthorizationServerSecurityConfigurer},
 *
 * @author 毕子航 951755883@qq.com
 * @date 2018/11/7
 */
@Configuration
@EnableAuthorizationServer
public class AppAuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
	@Autowired
	private UserDetailsService userDetailsService;

	@Autowired
	private AuthenticationManager authenticationManager;

	@Autowired
	private TokenStore tokenStore;

	@Autowired(required = false)
	private JwtAccessTokenConverter jwtAccessTokenConverter;

	@Autowired(required = false)
	private TokenEnhancer jwtTokenEnhancer;

	@Autowired
	private SecurityProperties securityProperties;

	/**
	 * 认证服务器的非安全特性的配置，像 token store,token customizations,user approvals 和grant type。
	 * 默认情况下你可以不配置，除非你需要password grant模式，在这个模式下你需要提供一个{@link AuthenticationManager}
	 */
	@Override
	public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
		endpoints.tokenStore(tokenStore)
				.userDetailsService(userDetailsService)
				.authenticationManager(authenticationManager);

		if (jwtAccessTokenConverter != null && jwtTokenEnhancer != null) {
			TokenEnhancerChain enhancerChain = new TokenEnhancerChain();
			List<TokenEnhancer> enhancers = new ArrayList<>();
			enhancers.add(jwtTokenEnhancer);
			enhancers.add(jwtAccessTokenConverter);
			enhancerChain.setTokenEnhancers(enhancers);
			endpoints.tokenEnhancer(enhancerChain).accessTokenConverter(jwtAccessTokenConverter);
		}
	}

	/**
	 * 认证服务器的安全配置:实际上意味着/oauth/token。/oauth/authorize端也需要安全，但是那是一个的普通的面向用户端并且应该
	 * 和rest的UI有相同的安全方式，因此这里不涉及到配置/oauth/authorize。默认设置覆盖了最常见的需求，以下推荐自oauth2,如果你不必
	 * 做任何事情也能跑起来。
	 */
	@Override
	public void configure(AuthorizationServerSecurityConfigurer http) throws Exception {
		http.tokenKeyAccess("permitAll()");
	}

	/**
	 * 配置{@link org.springframework.security.oauth2.provider.ClientDetailsService}.声明个人client和他们的属性。注意
	 * 除非{@link AuthenticationManager}应用于{@link #configure(AuthorizationServerEndpointsConfigurer)},否则password grant
	 * 不可用。至少一个client，或者完全形式定义{@link org.springframework.security.oauth2.provider.ClientDetailsService}必须被声名，
	 * 否则服务不会启动。
	 */
	@Override
	public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
		InMemoryClientDetailsServiceBuilder builder = clients.inMemory();
		if (ArrayUtils.isNotEmpty(securityProperties.getOauth2().getClients())) {
			for (OAuth2ClientProperties client : securityProperties.getOauth2().getClients()) {
				builder.withClient(client.getClientId())
						.secret(client.getClientSecret())
						.authorizedGrantTypes("refresh_token", "authorization_code", "password")
						.accessTokenValiditySeconds(client.getAccessTokenValidateSeconds())
						.refreshTokenValiditySeconds(2592000)
						.scopes("all");
			}
		}
	}
}
